/* Copyright (c) 2010 Daniel Doubrovkine, All Rights Reserved
 *
 * The contents of this file is dual-licensed under 2
 * alternative Open Source/Free licenses: LGPL 2.1 or later and
 * Apache License 2.0. (starting with JNA version 4.0.0).
 *
 * You can freely decide which license you want to apply to
 * the project.
 *
 * You may obtain a copy of the LGPL License at:
 *
 * http://www.gnu.org/licenses/licenses.html
 *
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "LGPL2.1".
 *
 * You may obtain a copy of the Apache License at:
 *
 * http://www.apache.org/licenses/
 *
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "AL2.0".
 */
package com.sun.jna.platform.win32;

import static com.sun.jna.platform.win32.WinBase.CREATE_FOR_DIR;
import static com.sun.jna.platform.win32.WinBase.CREATE_FOR_IMPORT;
import static com.sun.jna.platform.win32.WinNT.DACL_SECURITY_INFORMATION;
import static com.sun.jna.platform.win32.WinNT.FILE_ALL_ACCESS;
import static com.sun.jna.platform.win32.WinNT.FILE_GENERIC_EXECUTE;
import static com.sun.jna.platform.win32.WinNT.FILE_GENERIC_READ;
import static com.sun.jna.platform.win32.WinNT.FILE_GENERIC_WRITE;
import static com.sun.jna.platform.win32.WinNT.GENERIC_EXECUTE;
import static com.sun.jna.platform.win32.WinNT.GENERIC_READ;
import static com.sun.jna.platform.win32.WinNT.GENERIC_WRITE;
import static com.sun.jna.platform.win32.WinNT.GROUP_SECURITY_INFORMATION;
import static com.sun.jna.platform.win32.WinNT.OWNER_SECURITY_INFORMATION;
import static com.sun.jna.platform.win32.WinNT.PROTECTED_DACL_SECURITY_INFORMATION;
import static com.sun.jna.platform.win32.WinNT.PROTECTED_SACL_SECURITY_INFORMATION;
import static com.sun.jna.platform.win32.WinNT.SACL_SECURITY_INFORMATION;
import static com.sun.jna.platform.win32.WinNT.SE_DACL_PROTECTED;
import static com.sun.jna.platform.win32.WinNT.SE_SACL_PROTECTED;
import static com.sun.jna.platform.win32.WinNT.STANDARD_RIGHTS_READ;
import static com.sun.jna.platform.win32.WinNT.TOKEN_ADJUST_PRIVILEGES;
import static com.sun.jna.platform.win32.WinNT.TOKEN_DUPLICATE;
import static com.sun.jna.platform.win32.WinNT.TOKEN_IMPERSONATE;
import static com.sun.jna.platform.win32.WinNT.TOKEN_QUERY;
import static com.sun.jna.platform.win32.WinNT.UNPROTECTED_DACL_SECURITY_INFORMATION;
import static com.sun.jna.platform.win32.WinNT.UNPROTECTED_SACL_SECURITY_INFORMATION;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinBase.FE_EXPORT_FUNC;
import com.sun.jna.platform.win32.WinBase.FE_IMPORT_FUNC;
import com.sun.jna.platform.win32.WinBase.FILETIME;
import com.sun.jna.platform.win32.WinDef.BOOLByReference;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinDef.DWORDByReference;
import com.sun.jna.platform.win32.WinDef.ULONG;
import com.sun.jna.platform.win32.WinDef.ULONGByReference;
import com.sun.jna.platform.win32.WinNT.ACCESS_ACEStructure;
import com.sun.jna.platform.win32.WinNT.ACCESS_ALLOWED_ACE;
import com.sun.jna.platform.win32.WinNT.ACE_HEADER;
import com.sun.jna.platform.win32.WinNT.ACL;
import com.sun.jna.platform.win32.WinNT.EVENTLOGRECORD;
import com.sun.jna.platform.win32.WinNT.GENERIC_MAPPING;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
import com.sun.jna.platform.win32.WinNT.PRIVILEGE_SET;
import com.sun.jna.platform.win32.WinNT.PSID;
import com.sun.jna.platform.win32.WinNT.PSIDByReference;
import com.sun.jna.platform.win32.WinNT.SECURITY_DESCRIPTOR_RELATIVE;
import com.sun.jna.platform.win32.WinNT.SECURITY_IMPERSONATION_LEVEL;
import com.sun.jna.platform.win32.WinNT.SID_AND_ATTRIBUTES;
import com.sun.jna.platform.win32.WinNT.SID_NAME_USE;
import com.sun.jna.platform.win32.WinNT.TOKEN_ELEVATION;
import com.sun.jna.platform.win32.WinNT.TOKEN_TYPE;
import com.sun.jna.platform.win32.WinReg.HKEY;
import com.sun.jna.platform.win32.WinReg.HKEYByReference;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.win32.W32APITypeMapper;

/**
 * Advapi32 utility API.
 *
 * @author dblock[at]dblock.org
 */
public abstract class Advapi32Util {
  /**
   * An account.
   */
  public static class Account {
    /**
     * Account name.
     */
    public String name;

    /**
     * Account domain.
     */
    public String domain;

    /**
     * Account SID.
     */
    public byte[] sid;

    /**
     * String representation of the account SID.
     */
    public String sidString;

    /**
     * Account type, one of SID_NAME_USE.
     */
    public int accountType;

    /**
     * Fully qualified account name.
     */
    public String fqn;
  }


  /**
   * Retrieves the name of the user associated with the current thread.
   *
   * @return A user name.
   */
  public static String getUserName() {
    char[] buffer = new char[128];
    IntByReference len = new IntByReference(buffer.length);
    boolean result = Advapi32.INSTANCE.GetUserNameW(buffer, len);

    if (!result) {
      switch (Kernel32.INSTANCE.GetLastError()) {
        case W32Errors.ERROR_INSUFFICIENT_BUFFER:
          buffer = new char[len.getValue()];
          break;

        default:
          throw new Win32Exception(Native.getLastError());
      }

      result = Advapi32.INSTANCE.GetUserNameW(buffer, len);
    }

    if (!result) {
      throw new Win32Exception(Native.getLastError());
    }

    return Native.toString(buffer);
  }

  /**
   * Retrieves a security identifier (SID) for the account on the current
   * system.
   *
   * @param accountName
   *            Specifies the account name.
   * @return A structure containing the account SID;
   */
  public static Account getAccountByName(String accountName) {
    return getAccountByName(null, accountName);
  }

  /**
   * Retrieves a security identifier (SID) for a given account.
   *
   * @param systemName
   *            Name of the system.
   * @param accountName
   *            Account name.
   * @return A structure containing the account SID.
   */
  public static Account getAccountByName(String systemName, String accountName) {
    IntByReference pSid = new IntByReference(0);
    IntByReference cchDomainName = new IntByReference(0);
    PointerByReference peUse = new PointerByReference();

    if (Advapi32.INSTANCE.LookupAccountName(systemName, accountName, null,
      pSid, null, cchDomainName, peUse)) {
      throw new RuntimeException(
        "LookupAccountNameW was expected to fail with ERROR_INSUFFICIENT_BUFFER");
    }

    int rc = Kernel32.INSTANCE.GetLastError();
    if (pSid.getValue() == 0 || rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }

    Memory sidMemory = new Memory(pSid.getValue());
    PSID result = new PSID(sidMemory);
    char[] referencedDomainName = new char[cchDomainName.getValue() + 1];

    if (!Advapi32.INSTANCE.LookupAccountName(systemName, accountName,
      result, pSid, referencedDomainName, cchDomainName, peUse)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    Account account = new Account();
    account.accountType = peUse.getPointer().getInt(0);

    String[] accountNamePartsBs = accountName.split("\\\\", 2);
    String[] accountNamePartsAt = accountName.split("@", 2);

    if (accountNamePartsBs.length == 2) {
      account.name = accountNamePartsBs[1];
    } else if (accountNamePartsAt.length == 2) {
      account.name = accountNamePartsAt[0];
    } else {
      account.name = accountName;
    }

    if (cchDomainName.getValue() > 0) {
      account.domain = Native.toString(referencedDomainName);
      account.fqn = account.domain + "\\" + account.name;
    } else {
      account.fqn = account.name;
    }

    account.sid = result.getBytes();
    account.sidString = convertSidToStringSid(new PSID(account.sid));
    return account;
  }

  /**
   * Get the account by SID on the local system.
   *
   * @param sid SID.
   *
   * @return Account.
   */
  public static Account getAccountBySid(PSID sid) {
    return getAccountBySid(null, sid);
  }

  /**
   * Get the account by SID.
   *
   * @param systemName
   *            Name of the system.
   * @param sid
   *            SID.
   * @return Account.
   */
  public static Account getAccountBySid(String systemName, PSID sid) {
    // Max username length (UNLEN) is 256, add space for null
    IntByReference cchName = new IntByReference(257);
    // Max fully qualified domain name is 255, add space for null
    IntByReference cchDomainName = new IntByReference(256);
    PointerByReference peUse = new PointerByReference();

    char[] domainName = new char[cchDomainName.getValue()];
    char[] name = new char[cchName.getValue()];

    int rc = WinError.ERROR_SUCCESS;
    if (!Advapi32.INSTANCE.LookupAccountSid(systemName, sid, name, cchName,
      domainName, cchDomainName, peUse)) {
      rc = Kernel32.INSTANCE.GetLastError();
      if (rc != WinError.ERROR_NONE_MAPPED) {
        throw new Win32Exception(rc);
      }
    }

    Account account = new Account();
    if (rc == WinError.ERROR_NONE_MAPPED) {
      account.accountType = SID_NAME_USE.SidTypeUnknown;
      account.name = "NONE_MAPPED";
    } else {
      account.accountType = peUse.getPointer().getInt(0);
      account.name = Native.toString(name);
    }

    account.domain = Native.toString(domainName);
    if (account.domain.isEmpty()) {
      account.fqn = account.name;
    } else {
      account.fqn = account.domain + "\\" + account.name;
    }

    account.sid = sid.getBytes();
    account.sidString = convertSidToStringSid(sid);
    return account;
  }

  /**
   * Convert a security identifier (SID) to a string format suitable for
   * display, storage, or transmission.
   *
   * @param sid
   *            SID bytes.
   * @return String SID.
   */
  public static String convertSidToStringSid(PSID sid) {
    PointerByReference stringSid = new PointerByReference();
    if (!Advapi32.INSTANCE.ConvertSidToStringSid(sid, stringSid)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    Pointer ptr = stringSid.getValue();
    try {
      return ptr.getWideString(0);
    } finally {
      Kernel32Util.freeLocalMemory(ptr);
    }
  }

  /**
   * Convert a string representation of a security identifier (SID) to a
   * binary format.
   *
   * @param sidString
   *            String SID.
   * @return SID bytes.
   */
  public static byte[] convertStringSidToSid(String sidString) {
    PSIDByReference pSID = new PSIDByReference();
    if (!Advapi32.INSTANCE.ConvertStringSidToSid(sidString, pSID)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    PSID value = pSID.getValue();
    try {
      return value.getBytes();
    } finally {
      Kernel32Util.freeLocalMemory(value.getPointer());
    }
  }

  /**
   * Compares a SID to a well known SID and returns TRUE if they match.
   *
   * @param sidString
   *            String representation of a SID.
   * @param wellKnownSidType
   *            Member of the WELL_KNOWN_SID_TYPE enumeration to compare with
   *            the SID at pSid.
   * @return True if the SID is of the well-known type, false otherwise.
   */
  public static boolean isWellKnownSid(String sidString, int wellKnownSidType) {
    PSIDByReference pSID = new PSIDByReference();
    if (!Advapi32.INSTANCE.ConvertStringSidToSid(sidString, pSID)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    PSID value = pSID.getValue();
    try {
      return Advapi32.INSTANCE.IsWellKnownSid(value, wellKnownSidType);
    } finally {
      Kernel32Util.freeLocalMemory(value.getPointer());
    }
  }

  /**
   * Compares a SID to a well known SID and returns TRUE if they match.
   *
   * @param sidBytes
   *            Byte representation of a SID.
   * @param wellKnownSidType
   *            Member of the WELL_KNOWN_SID_TYPE enumeration to compare with
   *            the SID at pSid.
   * @return True if the SID is of the well-known type, false otherwise.
   */
  public static boolean isWellKnownSid(byte[] sidBytes, int wellKnownSidType) {
    PSID pSID = new PSID(sidBytes);
    return Advapi32.INSTANCE.IsWellKnownSid(pSID, wellKnownSidType);
  }

  /**
   * Align cbAcl on a DWORD
   * @param cbAcl size to align
   * @return the aligned size
   */
  public static int alignOnDWORD(int cbAcl) {
    return (cbAcl + (DWORD.SIZE - 1)) & 0xfffffffc;
  }

  /**
   * Helper function to calculate the size of an ACE for a given PSID size
   * @param sidLength length of the sid
   * @return size of the ACE
   */
  public static int getAceSize(int sidLength) {
    return Native.getNativeSize(ACCESS_ALLOWED_ACE.class, null)
      + sidLength
      - DWORD.SIZE;
  }

  /**
   * Get an account name from a string SID on the local machine.
   *
   * @param sidString
   *            SID.
   * @return Account.
   */
  public static Account getAccountBySid(String sidString) {
    return getAccountBySid(null, sidString);
  }

  /**
   * Get an account name from a string SID.
   *
   * @param systemName
   *            System name.
   * @param sidString
   *            SID.
   * @return Account.
   */
  public static Account getAccountBySid(String systemName, String sidString) {
    return getAccountBySid(systemName, new PSID(convertStringSidToSid(sidString)));
  }

  /**
   * This function returns the groups associated with a security token, such
   * as a user token.
   *
   * @param hToken
   *            Token.
   * @return Token groups.
   */
  public static Account[] getTokenGroups(HANDLE hToken) {
    // get token group information size
    IntByReference tokenInformationLength = new IntByReference();
    if (Advapi32.INSTANCE.GetTokenInformation(hToken,
      WinNT.TOKEN_INFORMATION_CLASS.TokenGroups, null, 0,
      tokenInformationLength)) {
      throw new RuntimeException(
        "Expected GetTokenInformation to fail with ERROR_INSUFFICIENT_BUFFER");
    }
    int rc = Kernel32.INSTANCE.GetLastError();
    if (rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    // get token group information
    WinNT.TOKEN_GROUPS groups = new WinNT.TOKEN_GROUPS(
      tokenInformationLength.getValue());
    if (!Advapi32.INSTANCE.GetTokenInformation(hToken,
      WinNT.TOKEN_INFORMATION_CLASS.TokenGroups, groups,
      tokenInformationLength.getValue(), tokenInformationLength)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }
    ArrayList<Account> userGroups = new ArrayList<Account>();
    // make array of names
    for (SID_AND_ATTRIBUTES sidAndAttribute : groups.getGroups()) {
      Account group;
      try {
        group = Advapi32Util.getAccountBySid(sidAndAttribute.Sid);
      } catch (Exception e) {
        group = new Account();
        group.sid = sidAndAttribute.Sid.getBytes();
        group.sidString = Advapi32Util
          .convertSidToStringSid(sidAndAttribute.Sid);
        group.name = group.sidString;
        group.fqn = group.sidString;
        group.accountType = SID_NAME_USE.SidTypeGroup;
      }
      userGroups.add(group);
    }
    return userGroups.toArray(new Account[0]);
  }

  /**
   * This function returns the primary group associated with a security token,
   * such as a user token.
   *
   * @param hToken
   *            Token.
   * @return Token primary group.
   */
  public static Account getTokenPrimaryGroup(HANDLE hToken) {
    // get token group information size
    IntByReference tokenInformationLength = new IntByReference();
    if (Advapi32.INSTANCE.GetTokenInformation(hToken, WinNT.TOKEN_INFORMATION_CLASS.TokenPrimaryGroup, null, 0,
      tokenInformationLength)) {
      throw new RuntimeException("Expected GetTokenInformation to fail with ERROR_INSUFFICIENT_BUFFER");
    }
    int rc = Kernel32.INSTANCE.GetLastError();
    if (rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    // get token group information
    WinNT.TOKEN_PRIMARY_GROUP primaryGroup = new WinNT.TOKEN_PRIMARY_GROUP(tokenInformationLength.getValue());
    if (!Advapi32.INSTANCE.GetTokenInformation(hToken, WinNT.TOKEN_INFORMATION_CLASS.TokenPrimaryGroup,
      primaryGroup, tokenInformationLength.getValue(), tokenInformationLength)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }
    Account group;
    try {
      group = Advapi32Util.getAccountBySid(primaryGroup.PrimaryGroup);
    } catch (Exception e) {
      group = new Account();
      group.sid = primaryGroup.PrimaryGroup.getBytes();
      group.sidString = Advapi32Util.convertSidToStringSid(primaryGroup.PrimaryGroup);
      group.name = group.sidString;
      group.fqn = group.sidString;
      group.accountType = SID_NAME_USE.SidTypeGroup;
    }
    return group;
  }

  /**
   * This function returns the information about the user who owns a security
   * token,
   *
   * @param hToken
   *            Token.
   * @return Token user.
   */
  public static Account getTokenAccount(HANDLE hToken) {
    // get token group information size
    IntByReference tokenInformationLength = new IntByReference();
    if (Advapi32.INSTANCE.GetTokenInformation(hToken,
      WinNT.TOKEN_INFORMATION_CLASS.TokenUser, null, 0,
      tokenInformationLength)) {
      throw new RuntimeException(
        "Expected GetTokenInformation to fail with ERROR_INSUFFICIENT_BUFFER");
    }
    int rc = Kernel32.INSTANCE.GetLastError();
    if (rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    // get token user information
    WinNT.TOKEN_USER user = new WinNT.TOKEN_USER(
      tokenInformationLength.getValue());
    if (!Advapi32.INSTANCE.GetTokenInformation(hToken,
      WinNT.TOKEN_INFORMATION_CLASS.TokenUser, user,
      tokenInformationLength.getValue(), tokenInformationLength)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }
    return getAccountBySid(user.User.Sid);
  }

  /**
   * Return the group memberships of the currently logged on user.
   *
   * @return An array of groups.
   */
  public static Account[] getCurrentUserGroups() {
    HANDLEByReference phToken = new HANDLEByReference();
    Win32Exception err = null;
    try {
      // open thread or process token
      HANDLE threadHandle = Kernel32.INSTANCE.GetCurrentThread();
      if (!Advapi32.INSTANCE.OpenThreadToken(threadHandle,
        TOKEN_DUPLICATE | TOKEN_QUERY, true, phToken)) {
        int rc = Kernel32.INSTANCE.GetLastError();
        if (rc != W32Errors.ERROR_NO_TOKEN) {
          throw new Win32Exception(rc);
        }

        HANDLE processHandle = Kernel32.INSTANCE.GetCurrentProcess();
        if (!Advapi32.INSTANCE.OpenProcessToken(processHandle,
          TOKEN_DUPLICATE | TOKEN_QUERY, phToken)) {
          throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }
      }

      return getTokenGroups(phToken.getValue());
    } catch(Win32Exception e) {
      err = e;
      throw err;    // re-throw in order to invoke finally block
    } finally {
      HANDLE hToken = phToken.getValue();
      if (!WinBase.INVALID_HANDLE_VALUE.equals(hToken)) {
        try {
          Kernel32Util.closeHandle(hToken);
        } catch(Win32Exception e) {
          if (err == null) {
            err = e;
          } else {
            err.addSuppressedReflected(e);
          }
        }
      }

      if (err != null) {
        throw err;
      }
    }
  }

  /**
   * Checks whether a registry key exists.
   *
   * @param root
   *            HKEY_LOCAL_MACHINE, etc.
   * @param key
   *            Path to the registry key.
   * @return True if the key exists.
   */
  public static boolean registryKeyExists(HKEY root, String key) {
    return registryKeyExists(root, key, 0);
  }

  /**
   * Checks whether a registry key exists.
   *
   * @param root
   *            HKEY_LOCAL_MACHINE, etc.
   * @param key
   *            Path to the registry key.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return True if the key exists.
   */
  public static boolean registryKeyExists(HKEY root, String key, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ | samDesiredExtra,
      phkKey);
    switch (rc) {
      case W32Errors.ERROR_SUCCESS:
        Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
        return true;
      case W32Errors.ERROR_FILE_NOT_FOUND:
        return false;
      default:
        throw new Win32Exception(rc);
    }
  }

  /**
   * Checks whether a registry value exists.
   *
   * @param root
   *            HKEY_LOCAL_MACHINE, etc.
   * @param key
   *            Registry key path.
   * @param value
   *            Value name.
   * @return True if the value exists.
   */
  public static boolean registryValueExists(HKEY root, String key, String value) {
    return registryValueExists(root, key, value, 0);
  }

  /**
   * Checks whether a registry value exists.
   *
   * @param root
   *            HKEY_LOCAL_MACHINE, etc.
   * @param key
   *            Registry key path.
   * @param value
   *            Value name.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return True if the value exists.
   */
  public static boolean registryValueExists(HKEY root, String key,
                                            String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ | samDesiredExtra,
      phkKey);
    switch (rc) {
      case W32Errors.ERROR_SUCCESS:
        break;
      case W32Errors.ERROR_FILE_NOT_FOUND:
        return false;
      default:
        throw new Win32Exception(rc);
    }
    try {
      IntByReference lpcbData = new IntByReference();
      IntByReference lpType = new IntByReference();
      rc = Advapi32.INSTANCE.RegQueryValueEx(phkKey.getValue(), value, 0,
        lpType, (Pointer) null, lpcbData);
      switch (rc) {
        case W32Errors.ERROR_SUCCESS:
        case W32Errors.ERROR_MORE_DATA:
        case W32Errors.ERROR_INSUFFICIENT_BUFFER:
          return true;
        case W32Errors.ERROR_FILE_NOT_FOUND:
          return false;
        default:
          throw new Win32Exception(rc);
      }
    } finally {
      if (!WinBase.INVALID_HANDLE_VALUE.equals(phkKey.getValue())) {
        rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
        if (rc != W32Errors.ERROR_SUCCESS) {
          throw new Win32Exception(rc);
        }
      }
    }
  }

  /**
   * Get a registry REG_SZ value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry path.
   * @param value
   *            Name of the value to retrieve.
   * @return String value.
   */
  public static String registryGetStringValue(HKEY root, String key,
                                              String value) {
    return registryGetStringValue(root, key, value, 0);
  }

  /**
   * Get a registry REG_SZ value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry path.
   * @param value
   *            Name of the value to retrieve.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return String value.
   */
  public static String registryGetStringValue(HKEY root, String key,
                                              String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ | samDesiredExtra ,
      phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryGetStringValue(phkKey.getValue(), value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Get a registry REG_SZ value.
   *
   * @param hKey
   *            Parent Key.
   * @param value
   *            Name of the value to retrieve.
   * @return String value.
   */
  public static String registryGetStringValue(HKEY hKey, String value) {
    IntByReference lpcbData = new IntByReference();
    IntByReference lpType = new IntByReference();
    int rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, (Pointer) null, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    if (lpType.getValue() != WinNT.REG_SZ
      && lpType.getValue() != WinNT.REG_EXPAND_SZ) {
      throw new RuntimeException("Unexpected registry type "
        + lpType.getValue()
        + ", expected REG_SZ or REG_EXPAND_SZ");
    }
    if (lpcbData.getValue() == 0) {
      return "";
    }
    // See comment in #registryGetValue
    Memory mem = new Memory(lpcbData.getValue() + Native.WCHAR_SIZE);
    mem.clear();
    rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, mem, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
      return mem.getWideString(0);
    } else {
      return mem.getString(0);
    }
  }

  /**
   * Get a registry REG_EXPAND_SZ value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry path.
   * @param value
   *            Name of the value to retrieve.
   * @return String value.
   */
  public static String registryGetExpandableStringValue(HKEY root,
                                                        String key, String value) {
    return registryGetExpandableStringValue(root, key, value, 0);
  }

  /**
   * Get a registry REG_EXPAND_SZ value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry path.
   * @param value
   *            Name of the value to retrieve.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return String value.
   */
  public static String registryGetExpandableStringValue(HKEY root,
                                                        String key, String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ | samDesiredExtra,
      phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryGetExpandableStringValue(phkKey.getValue(), value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Get a registry REG_EXPAND_SZ value.
   *
   * @param hKey
   *            Parent Key.
   * @param value
   *            Name of the value to retrieve.
   * @return String value.
   */
  public static String registryGetExpandableStringValue(HKEY hKey, String value) {
    IntByReference lpcbData = new IntByReference();
    IntByReference lpType = new IntByReference();
    int rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, (char[]) null, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    if (lpType.getValue() != WinNT.REG_EXPAND_SZ) {
      throw new RuntimeException("Unexpected registry type "
        + lpType.getValue() + ", expected REG_SZ");
    }
    if (lpcbData.getValue() == 0) {
      return "";
    }
    // See comment in #registryGetValue
    Memory mem = new Memory(lpcbData.getValue() + Native.WCHAR_SIZE);
    mem.clear();
    rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, mem, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
      return mem.getWideString(0);
    } else {
      return mem.getString(0);
    }
  }

  /**
   * Get a registry REG_MULTI_SZ value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry path.
   * @param value
   *            Name of the value to retrieve.
   * @return String value.
   */
  public static String[] registryGetStringArray(HKEY root, String key,
                                                String value) {
    return registryGetStringArray(root, key, value, 0);
  }

  /**
   * Get a registry REG_MULTI_SZ value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry path.
   * @param value
   *            Name of the value to retrieve.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return String value.
   */
  public static String[] registryGetStringArray(HKEY root, String key,
                                                String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ | samDesiredExtra,
      phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryGetStringArray(phkKey.getValue(), value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Get a registry REG_MULTI_SZ value.
   *
   * @param hKey
   *            Parent Key.
   * @param value
   *            Name of the value to retrieve.
   * @return String value.
   */
  public static String[] registryGetStringArray(HKEY hKey, String value) {
    IntByReference lpcbData = new IntByReference();
    IntByReference lpType = new IntByReference();
    int rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, (char[]) null, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    if (lpType.getValue() != WinNT.REG_MULTI_SZ) {
      throw new RuntimeException("Unexpected registry type "
        + lpType.getValue() + ", expected REG_SZ");
    }
    // Allocate enougth memroy to hold value and ensure terminating
    // double NULL chars are present
    Memory data = new Memory(lpcbData.getValue() + 2 * Native.WCHAR_SIZE);
    data.clear();
    rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, data, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    return regMultiSzBufferToStringArray(data);
  }

  /**
   * Convert the null-delimited buffer of strings returned from registry values of
   * type {@link WinNT#REG_MULTI_SZ} to an array of strings.
   *
   * @param data
   *            A buffer containing strings delimited by a null character, ending
   *            with two null characters.
   * @return An array of strings corresponding to the strings in the buffer.
   */
  static String[] regMultiSzBufferToStringArray(Memory data) {
    ArrayList<String> result = new ArrayList<String>();
    int offset = 0;
    while (offset < data.size()) {
      String s;
      if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
        s = data.getWideString(offset);
        offset += s.length() * Native.WCHAR_SIZE;
        offset += Native.WCHAR_SIZE;
      } else {
        s = data.getString(offset);
        offset += s.length();
        offset += 1;
      }

      if (s.length() == 0) {
        // A sequence of null-terminated strings,
        // terminated by an empty string (\0).
        // => The first empty string terminates the
        break;
      } else {
        result.add(s);
      }
    }
    return result.toArray(new String[0]);
  }

  /**
   * Get a registry REG_BINARY value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry path.
   * @param value
   *            Name of the value to retrieve.
   * @return String value.
   */
  public static byte[] registryGetBinaryValue(HKEY root, String key,
                                              String value) {
    return registryGetBinaryValue(root, key, value, 0);
  }

  /**
   * Get a registry REG_BINARY value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry path.
   * @param value
   *            Name of the value to retrieve.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return String value.
   */
  public static byte[] registryGetBinaryValue(HKEY root, String key,
                                              String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ | samDesiredExtra,
      phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryGetBinaryValue(phkKey.getValue(), value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Get a registry REG_BINARY value.
   *
   * @param hKey
   *            Parent Key.
   * @param value
   *            Name of the value to retrieve.
   * @return String value.
   */
  public static byte[] registryGetBinaryValue(HKEY hKey, String value) {
    IntByReference lpcbData = new IntByReference();
    IntByReference lpType = new IntByReference();
    int rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, (Pointer) null, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    if (lpType.getValue() != WinNT.REG_BINARY) {
      throw new RuntimeException("Unexpected registry type "
        + lpType.getValue() + ", expected REG_BINARY");
    }
    byte[] data = new byte[lpcbData.getValue()];
    rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, data, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    return data;
  }

  /**
   * Get a registry DWORD value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry key path.
   * @param value
   *            Name of the value to retrieve.
   * @return Integer value.
   */
  public static int registryGetIntValue(HKEY root, String key, String value) {
    return registryGetIntValue(root, key, value, 0);
  }

  /**
   * Get a registry DWORD value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry key path.
   * @param value
   *            Name of the value to retrieve.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return Integer value.
   */
  public static int registryGetIntValue(WinReg.HKEY root, String key, String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ | samDesiredExtra,
      phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryGetIntValue(phkKey.getValue(), value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Get a registry DWORD value.
   *
   * @param hKey
   *            Parent key.
   * @param value
   *            Name of the value to retrieve.
   * @return Integer value.
   */
  public static int registryGetIntValue(HKEY hKey, String value) {
    IntByReference lpcbData = new IntByReference();
    IntByReference lpType = new IntByReference();
    int rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, (char[]) null, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    if (lpType.getValue() != WinNT.REG_DWORD) {
      throw new RuntimeException("Unexpected registry type "
        + lpType.getValue() + ", expected REG_DWORD");
    }
    IntByReference data = new IntByReference();
    rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, data, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    return data.getValue();
  }

  /**
   * Get a registry QWORD value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry key path.
   * @param value
   *            Name of the value to retrieve.
   * @return Integer value.
   */
  public static long registryGetLongValue(HKEY root, String key, String value) {
    return registryGetLongValue(root, key, value, 0);
  }

  /**
   * Get a registry QWORD value.
   *
   * @param root
   *            Root key.
   * @param key
   *            Registry key path.
   * @param value
   *            Name of the value to retrieve.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return Integer value.
   */
  public static long registryGetLongValue(HKEY root, String key, String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ | samDesiredExtra,
      phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryGetLongValue(phkKey.getValue(), value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Get a registry QWORD value.
   *
   * @param hKey
   *            Parent key.
   * @param value
   *            Name of the value to retrieve.
   * @return Integer value.
   */
  public static long registryGetLongValue(HKEY hKey, String value) {
    IntByReference lpcbData = new IntByReference();
    IntByReference lpType = new IntByReference();
    int rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, (char[]) null, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    if (lpType.getValue() != WinNT.REG_QWORD) {
      throw new RuntimeException("Unexpected registry type "
        + lpType.getValue() + ", expected REG_QWORD");
    }
    LongByReference data = new LongByReference();
    rc = Advapi32.INSTANCE.RegQueryValueEx(hKey, value, 0,
      lpType, data, lpcbData);
    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }
    return data.getValue();
  }

  /**
   * Get a registry value and returns a java object depending on the value
   * type.
   *
   * @param hkKey
   *            Root key.
   * @param subKey
   *            Registry key path.
   * @param lpValueName
   *            Name of the value to retrieve or null for the default value.
   * @return Object value.
   */
  public static Object registryGetValue(HKEY hkKey, String subKey,
                                        String lpValueName) {
    Object result = null;
    IntByReference lpType = new IntByReference();
    IntByReference lpcbData = new IntByReference();

    int rc = Advapi32.INSTANCE.RegGetValue(hkKey, subKey, lpValueName,
      Advapi32.RRF_RT_ANY, lpType, (Pointer) null, lpcbData);

    // if lpType == 0 then the value is empty (REG_NONE)!
    if (lpType.getValue() == WinNT.REG_NONE)
      return null;

    if (rc != W32Errors.ERROR_SUCCESS
      && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
      throw new Win32Exception(rc);
    }

    // Buffer is intentionally allocated larger than
    // indicated, as function adds terminating NULL char, if it is
    // missing. WCHAR_SIZE is added, as returning string can be
    // char[] or wchar[] depending on w32.ascii
    Memory byteData = new Memory(lpcbData.getValue() + Native.WCHAR_SIZE);
    byteData.clear();

    rc = Advapi32.INSTANCE.RegGetValue(hkKey, subKey, lpValueName,
      Advapi32.RRF_RT_ANY, lpType, byteData, lpcbData);

    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }

    if (lpType.getValue() == WinNT.REG_DWORD) {
      result = byteData.getInt(0);
    } else if (lpType.getValue() == WinNT.REG_QWORD) {
      result = byteData.getLong(0);
    } else if (lpType.getValue() == WinNT.REG_BINARY) {
      result = byteData.getByteArray(0, lpcbData.getValue());
    } else if ((lpType.getValue() == WinNT.REG_SZ)
      || (lpType.getValue() == WinNT.REG_EXPAND_SZ)) {
      if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
        result = byteData.getWideString(0);
      } else {
        result = byteData.getString(0);
      }
    }

    return result;
  }

  /**
   * Create a registry key.
   *
   * @param hKey
   *            Parent key.
   * @param keyName
   *            Key name.
   * @return True if the key was created, false otherwise.
   */
  public static boolean registryCreateKey(HKEY hKey, String keyName) {
    return registryCreateKey(hKey, keyName, 0);
  }

  /**
   * Create a registry key.
   *
   * @param hKey
   *            Parent key.
   * @param keyName
   *            Key name.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return True if the key was created, false otherwise.
   */
  public static boolean registryCreateKey(WinReg.HKEY hKey, String keyName, int samDesiredExtra) {
    HKEYByReference phkResult = new HKEYByReference();
    IntByReference lpdwDisposition = new IntByReference();
    int rc = Advapi32.INSTANCE.RegCreateKeyEx(hKey, keyName, 0, null,
      WinNT.REG_OPTION_NON_VOLATILE, WinNT.KEY_READ | samDesiredExtra, null, phkResult,
      lpdwDisposition);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    rc = Advapi32.INSTANCE.RegCloseKey(phkResult.getValue());
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    return WinNT.REG_CREATED_NEW_KEY == lpdwDisposition.getValue();
  }

  /**
   * Create a registry key.
   *
   * @param root
   *            Root key.
   * @param parentPath
   *            Path to an existing registry key.
   * @param keyName
   *            Key name.
   * @return True if the key was created, false otherwise.
   */
  public static boolean registryCreateKey(HKEY root, String parentPath,
                                          String keyName) {
    return registryCreateKey(root, parentPath, keyName, 0);
  }

  /**
   * Create a registry key.
   *
   * @param root
   *            Root key.
   * @param parentPath
   *            Path to an existing registry key.
   * @param keyName
   *            Key name.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_CREATE_SUB_KEY.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return True if the key was created, false otherwise.
   */
  public static boolean registryCreateKey(HKEY root, String parentPath,
                                          String keyName, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, parentPath, 0,
      WinNT.KEY_CREATE_SUB_KEY | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryCreateKey(phkKey.getValue(), keyName);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Set an integer value in registry.
   *
   * @param hKey
   *            Parent key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   */
  public static void registrySetIntValue(HKEY hKey, String name, int value) {
    byte[] data = new byte[4];
    data[0] = (byte) (value & 0xff);
    data[1] = (byte) ((value >> 8) & 0xff);
    data[2] = (byte) ((value >> 16) & 0xff);
    data[3] = (byte) ((value >> 24) & 0xff);
    int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0,
      WinNT.REG_DWORD, data, 4);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Set an integer value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   */
  public static void registrySetIntValue(HKEY root, String keyPath,
                                         String name, int value) {
    registrySetIntValue(root, keyPath, name, value, 0);
  }

  /**
   * Set an integer value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ and WinNT.KEY_WRITE.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   */
  public static void registrySetIntValue(WinReg.HKEY root, String keyPath,
                                         String name, int value, int samDesiredExtra) {
    WinReg.HKEYByReference phkKey = new WinReg.HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | WinNT.KEY_WRITE | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      registrySetIntValue(phkKey.getValue(), name, value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Set a long value in registry.
   *
   * @param hKey
   *            Parent key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   */
  public static void registrySetLongValue(HKEY hKey, String name, long value) {
    byte[] data = new byte[8];
    data[0] = (byte) (value & 0xff);
    data[1] = (byte) ((value >> 8) & 0xff);
    data[2] = (byte) ((value >> 16) & 0xff);
    data[3] = (byte) ((value >> 24) & 0xff);
    data[4] = (byte) ((value >> 32) & 0xff);
    data[5] = (byte) ((value >> 40) & 0xff);
    data[6] = (byte) ((value >> 48) & 0xff);
    data[7] = (byte) ((value >> 56) & 0xff);
    int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0,
      WinNT.REG_QWORD, data, 8);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Set a long value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   */
  public static void registrySetLongValue(HKEY root, String keyPath,
                                          String name, long value) {
    registrySetLongValue(root, keyPath, name, value, 0);
  }

  /**
   * Set a long value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ and WinNT.KEY_WRITE.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   */
  public static void registrySetLongValue(HKEY root, String keyPath,
                                          String name, long value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | WinNT.KEY_WRITE | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      registrySetLongValue(phkKey.getValue(), name, value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Set a string value in registry.
   *
   * @param hKey
   *            Parent key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   */
  public static void registrySetStringValue(HKEY hKey, String name,
                                            String value) {
    if (value == null) {
      value = "";
    }
    Memory data;
    if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
      data = new Memory((value.length() + 1) * Native.WCHAR_SIZE);
      data.setWideString(0, value);
    } else {
      data = new Memory((value.length() + 1));
      data.setString(0, value);
    }
    int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0, WinNT.REG_SZ,
      data, (int) data.size());
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Set a string value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   */
  public static void registrySetStringValue(HKEY root, String keyPath,
                                            String name, String value) {
    registrySetStringValue(root, keyPath, name, value, 0);
  }

  /**
   * Set a string value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ and WinNT.KEY_WRITE.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   */
  public static void registrySetStringValue(HKEY root, String keyPath,
                                            String name, String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | WinNT.KEY_WRITE | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      registrySetStringValue(phkKey.getValue(), name, value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Set an expandable string value in registry.
   *
   * @param hKey
   *            Parent key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   */
  public static void registrySetExpandableStringValue(HKEY hKey, String name,
                                                      String value) {
    Memory data;
    if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
      data = new Memory((value.length() + 1) * Native.WCHAR_SIZE);
      data.setWideString(0, value);
    } else {
      data = new Memory((value.length() + 1));
      data.setString(0, value);
    }
    int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0,
      WinNT.REG_EXPAND_SZ, data, (int) data.size());
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Set a string value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   */
  public static void registrySetExpandableStringValue(HKEY root,
                                                      String keyPath, String name, String value) {
    registrySetExpandableStringValue(root, keyPath, name, value, 0);
  }

  /**
   * Set a string value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param value
   *            Value to write to registry.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ and WinNT.KEY_WRITE.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   */
  public static void registrySetExpandableStringValue(HKEY root,
                                                      String keyPath, String name, String value, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | WinNT.KEY_WRITE | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      registrySetExpandableStringValue(phkKey.getValue(), name, value);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Set a string array value in registry.
   *
   * @param hKey
   *            Parent key.
   * @param name
   *            Name.
   * @param arr
   *            Array of strings to write to registry.
   */
  public static void registrySetStringArray(HKEY hKey, String name,
                                            String[] arr) {

    int charwidth = W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ? Native.WCHAR_SIZE : 1;

    int size = 0;
    for (String s : arr) {
      size += s.length() * charwidth;
      size += charwidth;
    }
    size += charwidth;

    int offset = 0;
    Memory data = new Memory(size);
    data.clear();
    for (String s : arr) {
      if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
        data.setWideString(offset, s);
      } else {
        data.setString(offset, s);
      }
      offset += s.length() * charwidth;
      offset += charwidth;
    }

    int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0,
      WinNT.REG_MULTI_SZ, data, size);

    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Set a string array value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param arr
   *            Array of strings to write to registry.
   */
  public static void registrySetStringArray(HKEY root, String keyPath,
                                            String name, String[] arr) {
    registrySetStringArray(root, keyPath, name, arr, 0);
  }

  /**
   * Set a string array value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param arr
   *            Array of strings to write to registry.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ and WinNT.KEY_WRITE.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   */
  public static void registrySetStringArray(HKEY root, String keyPath,
                                            String name, String[] arr, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | WinNT.KEY_WRITE | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      registrySetStringArray(phkKey.getValue(), name, arr);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Set a binary value in registry.
   *
   * @param hKey
   *            Parent key.
   * @param name
   *            Value name.
   * @param data
   *            Data to write to registry.
   */
  public static void registrySetBinaryValue(HKEY hKey, String name,
                                            byte[] data) {
    int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0,
      WinNT.REG_BINARY, data, data.length);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Set a binary value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param data
   *            Data to write to registry.
   */
  public static void registrySetBinaryValue(HKEY root, String keyPath,
                                            String name, byte[] data) {
    registrySetBinaryValue(root, keyPath, name, data, 0);
  }

  /**
   * Set a binary value in registry.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param name
   *            Value name.
   * @param data
   *            Data to write to registry.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ and WinNT.KEY_WRITE.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   */
  public static void registrySetBinaryValue(HKEY root, String keyPath,
                                            String name, byte[] data, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | WinNT.KEY_WRITE | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      registrySetBinaryValue(phkKey.getValue(), name, data);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Delete a registry key.
   *
   * @param hKey
   *            Parent key.
   * @param keyName
   *            Name of the key to delete.
   */
  public static void registryDeleteKey(HKEY hKey, String keyName) {
    int rc = Advapi32.INSTANCE.RegDeleteKey(hKey, keyName);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Delete a registry key.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param keyName
   *            Name of the key to delete.
   */
  public static void registryDeleteKey(HKEY root, String keyPath,
                                       String keyName) {
    registryDeleteKey(root, keyPath, keyName, 0);
  }

  /**
   * Delete a registry key.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param keyName
   *            Name of the key to delete.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ and WinNT.KEY_WRITE.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   */
  public static void registryDeleteKey(HKEY root, String keyPath,
                                       String keyName, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | WinNT.KEY_WRITE | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      registryDeleteKey(phkKey.getValue(), keyName);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Delete a registry value.
   *
   * @param hKey
   *            Parent key.
   * @param valueName
   *            Name of the value to delete.
   */
  public static void registryDeleteValue(HKEY hKey, String valueName) {
    int rc = Advapi32.INSTANCE.RegDeleteValue(hKey, valueName);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Delete a registry value.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param valueName
   *            Name of the value to delete.
   */
  public static void registryDeleteValue(HKEY root, String keyPath,
                                         String valueName) {
    registryDeleteValue(root, keyPath, valueName, 0);
  }

  /**
   * Delete a registry value.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to an existing registry key.
   * @param valueName
   *            Name of the value to delete.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ and WinNT.KEY_WRITE.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   */
  public static void registryDeleteValue(HKEY root, String keyPath,
                                         String valueName, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | WinNT.KEY_WRITE | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      registryDeleteValue(phkKey.getValue(), valueName);
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Get names of the registry key's sub-keys.
   *
   * @param hKey
   *            Registry key.
   * @return Array of registry key names.
   */
  public static String[] registryGetKeys(HKEY hKey) {
    IntByReference lpcSubKeys = new IntByReference();
    IntByReference lpcMaxSubKeyLen = new IntByReference();
    int rc = Advapi32.INSTANCE
      .RegQueryInfoKey(hKey, null, null, null, lpcSubKeys,
        lpcMaxSubKeyLen, null, null, null, null, null, null);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    ArrayList<String> keys = new ArrayList<String>(lpcSubKeys.getValue());
    char[] name = new char[lpcMaxSubKeyLen.getValue() + 1];
    for (int i = 0; i < lpcSubKeys.getValue(); i++) {
      IntByReference lpcchValueName = new IntByReference(
        lpcMaxSubKeyLen.getValue() + 1);
      rc = Advapi32.INSTANCE.RegEnumKeyEx(hKey, i, name, lpcchValueName,
        null, null, null, null);
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
      keys.add(Native.toString(name));
    }
    return keys.toArray(new String[0]);
  }

  /**
   * Get names of the registry key's sub-keys.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to a registry key.
   * @return Array of registry key names.
   */
  public static String[] registryGetKeys(HKEY root, String keyPath) {
    return registryGetKeys(root, keyPath, 0);
  }

  /**
   * Get names of the registry key's sub-keys.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to a registry key.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return Array of registry key names.
   */
  public static String[] registryGetKeys(HKEY root, String keyPath, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryGetKeys(phkKey.getValue());
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Get a registry key, the caller is responsible to close the key after use.
   *
   * @param root
   *            Root key.
   * @param keyPath
   *            Path to a registry key.
   *
   * @param samDesired
   *            Access level (e.g. WinNT.KEY_READ)
   *
   * @return HKEYByReference.
   */
  public static HKEYByReference registryGetKey(HKEY root, String keyPath,
                                               int samDesired) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, samDesired,
      phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }

    return phkKey;
  }

  /**
   * Loads the specified registry hive as an application hive.
   *
   * @param fileName
   *            Path to the file
   * @param samDesired
   *            Access mask that specifies the desired access rights to the
   * @param dwOptions
   *            If this parameter is REG_PROCESS_APPKEY,
   *            the hive cannot be loaded again while it is loaded by the caller.
   *            This prevents access to this registry hive by another caller.
   */
  public static HKEYByReference registryLoadAppKey(String fileName, int samDesired, int dwOptions) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegLoadAppKey(fileName, phkKey, samDesired, dwOptions, 0);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }

    return phkKey;
  }

  /**
   * Close the registry key
   *
   * @param hKey
   *            Registry key.
   */
  public static void registryCloseKey(HKEY hKey) {
    int rc = Advapi32.INSTANCE.RegCloseKey(hKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
  }

  /**
   * Get a table of registry values.
   *
   * @param hKey
   *            Registry key.
   * @return Table of values.
   */
  public static TreeMap<String, Object> registryGetValues(HKEY hKey) {
    IntByReference lpcValues = new IntByReference();
    IntByReference lpcMaxValueNameLen = new IntByReference();
    IntByReference lpcMaxValueLen = new IntByReference();
    int rc = Advapi32.INSTANCE.RegQueryInfoKey(hKey, null, null, null,
      null, null, null, lpcValues, lpcMaxValueNameLen,
      lpcMaxValueLen, null, null);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    TreeMap<String, Object> keyValues = new TreeMap<String, Object>();
    char[] name = new char[lpcMaxValueNameLen.getValue() + 1];
    // Allocate enough memory to hold largest value and two
    // terminating WCHARs -- the memory is zeroed so after
    // value request we should not overread when reading strings
    Memory byteData = new Memory(lpcMaxValueLen.getValue() + 2 * Native.WCHAR_SIZE);
    for (int i = 0; i < lpcValues.getValue(); i++) {
      byteData.clear();
      IntByReference lpcchValueName = new IntByReference(
        lpcMaxValueNameLen.getValue() + 1);
      IntByReference lpcbData = new IntByReference(
        lpcMaxValueLen.getValue());
      IntByReference lpType = new IntByReference();
      rc = Advapi32.INSTANCE.RegEnumValue(hKey, i, name, lpcchValueName,
        null, lpType, byteData, lpcbData);
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }

      String nameString = Native.toString(name);

      if (lpcbData.getValue() == 0) {
        switch (lpType.getValue()) {
          case WinNT.REG_BINARY: {
            keyValues.put(nameString, new byte[0]);
            break;
          }
          case WinNT.REG_SZ:
          case WinNT.REG_EXPAND_SZ: {
            keyValues.put(nameString, new char[0]);
            break;
          }
          case WinNT.REG_MULTI_SZ: {
            keyValues.put(nameString, new String[0]);
            break;
          }
          case WinNT.REG_NONE: {
            keyValues.put(nameString, null);
            break;
          }
          default:
            throw new RuntimeException("Unsupported empty type: "
              + lpType.getValue());
        }
        continue;
      }

      switch (lpType.getValue()) {
        case WinNT.REG_QWORD: {
          keyValues.put(nameString, byteData.getLong(0));
          break;
        }
        case WinNT.REG_DWORD: {
          keyValues.put(nameString, byteData.getInt(0));
          break;
        }
        case WinNT.REG_SZ:
        case WinNT.REG_EXPAND_SZ: {
          if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
            keyValues.put(nameString, byteData.getWideString(0));
          } else {
            keyValues.put(nameString, byteData.getString(0));
          }
          break;
        }
        case WinNT.REG_BINARY: {
          keyValues.put(nameString,
            byteData.getByteArray(0, lpcbData.getValue()));
          break;
        }
        case WinNT.REG_MULTI_SZ: {
          ArrayList<String> result = new ArrayList<String>();
          int offset = 0;
          while (offset < byteData.size()) {
            String s;
            if (W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE) {
              s = byteData.getWideString(offset);
              offset += s.length() * Native.WCHAR_SIZE;
              offset += Native.WCHAR_SIZE;
            } else {
              s = byteData.getString(offset);
              offset += s.length();
              offset += 1;
            }

            if (s.length() == 0) {
              // A sequence of null-terminated strings,
              // terminated by an empty string (\0).
              // => The first empty string terminates the
              break;
            } else {
              result.add(s);
            }
          }
          keyValues.put(nameString, result.toArray(new String[0]));
          break;
        }
        default:
          throw new RuntimeException("Unsupported type: "
            + lpType.getValue());
      }
    }
    return keyValues;
  }

  /**
   * Get a table of registry values.
   *
   * @param root
   *            Registry root.
   * @param keyPath
   *            Regitry key path.
   * @return Table of values.
   */
  public static TreeMap<String, Object> registryGetValues(HKEY root,
                                                          String keyPath) {
    return registryGetValues(root, keyPath, 0);
  }

  /**
   * Get a table of registry values.
   *
   * @param root
   *            Registry root.
   * @param keyPath
   *            Regitry key path.
   * @param samDesiredExtra
   *            Registry key security and access rights to be requested in addition to WinNT.KEY_READ.
   *            (e.g WinNT.KEY_WOW64_32KEY or WinNT.KEY_WOW64_64KEY to force 32bit or 64bit registry access.)
   * @return Table of values.
   */
  public static TreeMap<String, Object> registryGetValues(HKEY root,
                                                          String keyPath, int samDesiredExtra) {
    HKEYByReference phkKey = new HKEYByReference();
    int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0,
      WinNT.KEY_READ | samDesiredExtra, phkKey);
    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }
    try {
      return registryGetValues(phkKey.getValue());
    } finally {
      rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
      if (rc != W32Errors.ERROR_SUCCESS) {
        throw new Win32Exception(rc);
      }
    }
  }

  /**
   * Queries the information about a specified key.
   *
   * @param hKey
   *            Current registry key.
   * @param lpcbSecurityDescriptor security descriptor
   *
   * @return A InfoKey value object.
   */
  public static InfoKey registryQueryInfoKey(HKEY hKey,
                                             int lpcbSecurityDescriptor) {

    InfoKey infoKey = new InfoKey(hKey, lpcbSecurityDescriptor);
    int rc = Advapi32.INSTANCE.RegQueryInfoKey(hKey, infoKey.lpClass,
      infoKey.lpcClass, null, infoKey.lpcSubKeys,
      infoKey.lpcMaxSubKeyLen, infoKey.lpcMaxClassLen,
      infoKey.lpcValues, infoKey.lpcMaxValueNameLen,
      infoKey.lpcMaxValueLen, infoKey.lpcbSecurityDescriptor,
      infoKey.lpftLastWriteTime);

    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }

    return infoKey;
  }

  public static class InfoKey {
    public HKEY hKey;
    public char[] lpClass = new char[WinNT.MAX_PATH];
    public IntByReference lpcClass = new IntByReference(WinNT.MAX_PATH);
    public IntByReference lpcSubKeys = new IntByReference();
    public IntByReference lpcMaxSubKeyLen = new IntByReference();
    public IntByReference lpcMaxClassLen = new IntByReference();
    public IntByReference lpcValues = new IntByReference();
    public IntByReference lpcMaxValueNameLen = new IntByReference();
    public IntByReference lpcMaxValueLen = new IntByReference();
    public IntByReference lpcbSecurityDescriptor = new IntByReference();
    public FILETIME lpftLastWriteTime = new FILETIME();

    public InfoKey() {
    }

    public InfoKey(HKEY hKey, int securityDescriptor) {
      this.hKey = hKey;
      this.lpcbSecurityDescriptor = new IntByReference(securityDescriptor);
    }
  }

  /**
   * Queries the information about a specified key.
   *
   * @param hKey
   *            Current registry key.
   * @param dwIndex
   *
   * @return A InfoKey value object.
   */
  public static EnumKey registryRegEnumKey(HKEY hKey, int dwIndex) {
    EnumKey enumKey = new EnumKey(hKey, dwIndex);
    int rc = Advapi32.INSTANCE.RegEnumKeyEx(hKey, enumKey.dwIndex,
      enumKey.lpName, enumKey.lpcName, null, enumKey.lpClass,
      enumKey.lpcbClass, enumKey.lpftLastWriteTime);

    if (rc != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(rc);
    }

    return enumKey;
  }

  public static class EnumKey {
    public HKEY hKey;
    public int dwIndex = 0;
    public char[] lpName = new char[Advapi32.MAX_KEY_LENGTH];
    public IntByReference lpcName = new IntByReference(
      Advapi32.MAX_KEY_LENGTH);
    public char[] lpClass = new char[Advapi32.MAX_KEY_LENGTH];
    public IntByReference lpcbClass = new IntByReference(
      Advapi32.MAX_KEY_LENGTH);
    public FILETIME lpftLastWriteTime = new FILETIME();

    public EnumKey() {
    }

    public EnumKey(HKEY hKey, int dwIndex) {
      this.hKey = hKey;
      this.dwIndex = dwIndex;
    }
  }

  /**
   * Converts a map of environment variables to an environment block suitable
   * for {@link Advapi32#CreateProcessAsUser}. This environment block consists
   * of null-terminated blocks of null-terminated strings. Each string is in
   * the following form: name=value\0
   *
   * @param environment
   *            Environment variables
   * @return A environment block
   */
  public static String getEnvironmentBlock(Map<String, String> environment) {
    StringBuilder out = new StringBuilder(environment.size() * 32 /* some guess about average name=value length*/);
    for (Entry<String, String> entry : environment.entrySet()) {
      String    key=entry.getKey(), value=entry.getValue();
      if (value != null) {
        out.append(key).append("=").append(value).append('\0');
      }
    }
    return out.append('\0').toString();
  }

  /**
   * Event log types.
   */
  public static enum EventLogType {
    Error, Warning, Informational, AuditSuccess, AuditFailure
  }

  /**
   * An event log record.
   */
  public static class EventLogRecord {
    private EVENTLOGRECORD _record;
    private String _source;
    private byte[] _data;
    private String[] _strings;

    /**
     * Raw record data.
     *
     * @return EVENTLOGRECORD.
     */
    public EVENTLOGRECORD getRecord() {
      return _record;
    }

    /**
     * The Instance ID, a resource identifier that corresponds to a string
     * definition in the message resource file of the event source. The
     * Event ID is the Instance ID with the top two bits masked off.
     *
     * @return An integer representing the 32-bit Instance ID.
     */
    public int getInstanceId() {
      return _record.EventID.intValue();
    }

    /**
     * @deprecated As of 5.4.0, replaced by {@link #getInstanceId()}. The
     *             Event ID displayed in the Windows Event Viewer
     *             corresponds to {@link #getStatusCode()} for
     *             system-generated events.
     */
    @Deprecated
    public int getEventId() {
      return _record.EventID.intValue();
    }

    /**
     * Event source.
     *
     * @return String.
     */
    public String getSource() {
      return _source;
    }

    /**
     * Status code, the rightmost 16 bits of the Instance ID. Corresponds to
     * the Event ID field in the Windows Event Viewer for system-generated
     * events.
     *
     * @return An integer representing the low 16-bits of the Instance ID.
     */
    public int getStatusCode() {
      return _record.EventID.intValue() & 0xFFFF;
    }

    /**
     * Record number of the record. This value can be used with the
     * EVENTLOG_SEEK_READ flag in the ReadEventLog function to begin reading
     * at a specified record.
     *
     * @return Integer.
     */
    public int getRecordNumber() {
      return _record.RecordNumber.intValue();
    }

    /**
     * Record length, with data.
     *
     * @return Number of bytes in the record including data.
     */
    public int getLength() {
      return _record.Length.intValue();
    }

    /**
     * Strings associated with this event.
     *
     * @return Array of strings or null.
     */
    public String[] getStrings() {
      return _strings;
    }

    /**
     * Event log type.
     *
     * @return Event log type.
     */
    public EventLogType getType() {
      switch (_record.EventType.intValue()) {
        case WinNT.EVENTLOG_SUCCESS:
        case WinNT.EVENTLOG_INFORMATION_TYPE:
          return EventLogType.Informational;
        case WinNT.EVENTLOG_AUDIT_FAILURE:
          return EventLogType.AuditFailure;
        case WinNT.EVENTLOG_AUDIT_SUCCESS:
          return EventLogType.AuditSuccess;
        case WinNT.EVENTLOG_ERROR_TYPE:
          return EventLogType.Error;
        case WinNT.EVENTLOG_WARNING_TYPE:
          return EventLogType.Warning;
        default:
          throw new RuntimeException("Invalid type: "
            + _record.EventType.intValue());
      }
    }

    /**
     * Raw data associated with the record.
     *
     * @return Array of bytes or null.
     */
    public byte[] getData() {
      return _data;
    }

    public EventLogRecord(Pointer pevlr) {
      _record = new EVENTLOGRECORD(pevlr);
      _source = pevlr.getWideString(_record.size());
      // data
      if (_record.DataLength.intValue() > 0) {
        _data = pevlr.getByteArray(_record.DataOffset.intValue(),
          _record.DataLength.intValue());
      }
      // strings
      if (_record.NumStrings.intValue() > 0) {
        ArrayList<String> strings = new ArrayList<String>();
        int count = _record.NumStrings.intValue();
        long offset = _record.StringOffset.intValue();
        while (count > 0) {
          String s = pevlr.getWideString(offset);
          strings.add(s);
          offset += s.length() * Native.WCHAR_SIZE;
          offset += Native.WCHAR_SIZE;
          count--;
        }
        _strings = strings.toArray(new String[0]);
      }
    }
  }

  /**
   * An iterator for Event Log entries.
   */
  public static class EventLogIterator implements Iterable<EventLogRecord>,
    Iterator<EventLogRecord> {

    private HANDLE _h;
    private Memory _buffer = new Memory(1024 * 64); // memory buffer to
    // store events
    private boolean _done = false; // no more events
    private int _dwRead = 0; // number of bytes remaining in the current
    // buffer
    private Pointer _pevlr = null; // pointer to the current record
    private int _flags;

    public EventLogIterator(String sourceName) {
      this(null, sourceName, WinNT.EVENTLOG_FORWARDS_READ);
    }

    public EventLogIterator(String serverName, String sourceName, int flags) {
      _flags = flags;
      _h = Advapi32.INSTANCE.OpenEventLog(serverName, sourceName);
      if (_h == null) {
        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
      }
    }

    private boolean read() {
      // finished or bytes remain, don't read any new data
      if (_done || _dwRead > 0) {
        return false;
      }

      IntByReference pnBytesRead = new IntByReference();
      IntByReference pnMinNumberOfBytesNeeded = new IntByReference();

      if (!Advapi32.INSTANCE
        .ReadEventLog(_h, WinNT.EVENTLOG_SEQUENTIAL_READ | _flags,
          0, _buffer, (int) _buffer.size(), pnBytesRead,
          pnMinNumberOfBytesNeeded)) {

        int rc = Kernel32.INSTANCE.GetLastError();

        // not enough bytes in the buffer, resize
        if (rc == W32Errors.ERROR_INSUFFICIENT_BUFFER) {
          _buffer = new Memory(pnMinNumberOfBytesNeeded.getValue());

          if (!Advapi32.INSTANCE.ReadEventLog(_h,
            WinNT.EVENTLOG_SEQUENTIAL_READ | _flags, 0,
            _buffer, (int) _buffer.size(), pnBytesRead,
            pnMinNumberOfBytesNeeded)) {
            throw new Win32Exception(
              Kernel32.INSTANCE.GetLastError());
          }
        } else {
          // read failed, no more entries or error
          close();
          if (rc != W32Errors.ERROR_HANDLE_EOF) {
            throw new Win32Exception(rc);
          }
          return false;
        }
      }

      _dwRead = pnBytesRead.getValue();
      _pevlr = _buffer;
      return true;
    }

    /**
     * Call close() in the case when the caller needs to abandon the
     * iterator before the iteration completes.
     */
    public void close() {
      _done = true;
      if (_h != null) {
        if (!Advapi32.INSTANCE.CloseEventLog(_h)) {
          throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }
        _h = null;
      }
    }

    // @Override - @todo restore Override annotation after we move to source
    // level 1.6
    @Override
    public Iterator<EventLogRecord> iterator() {
      return this;
    }

    // @Override - @todo restore Override annotation after we move to source
    // level 1.6
    @Override
    public boolean hasNext() {
      read();
      return !_done;
    }

    // @Override - @todo restore Override annotation after we move to source
    // level 1.6
    @Override
    public EventLogRecord next() {
      read();
      EventLogRecord record = new EventLogRecord(_pevlr);
      _dwRead -= record.getLength();
      _pevlr = _pevlr.share(record.getLength());
      return record;
    }

    // @Override - @todo restore Override annotation after we move to source
    // level 1.6
    @Override
    public void remove() {
    }
  }

  /**
   * @param fileName path to the file
   * @param compact if true compatible ACEs are merged if possible
   * @return list of ACEs in the DACL of the referenced file
   */
  public static ACE_HEADER[] getFileSecurity(String fileName,
                                             boolean compact) {
    int infoType = WinNT.DACL_SECURITY_INFORMATION;
    int nLength = 1024;
    boolean repeat;
    Memory memory;

    do {
      repeat = false;
      memory = new Memory(nLength);
      IntByReference lpnSize = new IntByReference();
      boolean succeded = Advapi32.INSTANCE.GetFileSecurity(
        fileName, infoType, memory, nLength, lpnSize);

      if (!succeded) {
        int lastError = Kernel32.INSTANCE.GetLastError();
        memory.clear();
        if (W32Errors.ERROR_INSUFFICIENT_BUFFER != lastError) {
          throw new Win32Exception(lastError);
        }
      }
      int lengthNeeded = lpnSize.getValue();
      if (nLength < lengthNeeded) {
        repeat = true;
        nLength = lengthNeeded;
        memory.clear();
      }
    } while (repeat);

    SECURITY_DESCRIPTOR_RELATIVE sdr = new WinNT.SECURITY_DESCRIPTOR_RELATIVE(
      memory);
    ACL dacl = sdr.getDiscretionaryACL();
    ACE_HEADER[] aceStructures = dacl.getACEs();

    if (compact) {
      List<ACE_HEADER> result = new ArrayList<ACE_HEADER>();
      Map<String, ACCESS_ACEStructure> aceMap = new HashMap<String, ACCESS_ACEStructure>();
      for (ACE_HEADER aceStructure : aceStructures) {
        if (aceStructure instanceof ACCESS_ACEStructure) {
          ACCESS_ACEStructure accessACEStructure = (ACCESS_ACEStructure) aceStructure;
          boolean inherted = ((aceStructure.AceFlags & WinNT.VALID_INHERIT_FLAGS) != 0);
          String key = accessACEStructure.getSidString() + "/" + inherted + "/"
            + aceStructure.getClass().getName();
          ACCESS_ACEStructure aceStructure2 = aceMap.get(key);
          if (aceStructure2 != null) {
            int accessMask = aceStructure2.Mask;
            accessMask = accessMask | accessACEStructure.Mask;
            aceStructure2.Mask = accessMask;
          } else {
            aceMap.put(key, accessACEStructure);
            result.add(aceStructure2);
          }
        } else {
          result.add(aceStructure);
        }
      }
      return result.toArray(new ACE_HEADER[0]);
    }

    return aceStructures;
  }

  public static enum AccessCheckPermission {
    READ(GENERIC_READ),
    WRITE(GENERIC_WRITE),
    EXECUTE(GENERIC_EXECUTE);

    final int code;

    AccessCheckPermission(int code) {
      this.code = code;
    }

    public int getCode() {
      return code;
    }
  }


  private static Memory getSecurityDescriptorForFile(final String absoluteFilePath) {
    final int infoType = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
      DACL_SECURITY_INFORMATION;

    final IntByReference lpnSize = new IntByReference();
    boolean succeeded = Advapi32.INSTANCE.GetFileSecurity(
      absoluteFilePath,
      infoType,
      null,
      0, lpnSize);

    if (!succeeded) {
      final int lastError = Kernel32.INSTANCE.GetLastError();
      if (W32Errors.ERROR_INSUFFICIENT_BUFFER != lastError) {
        throw new Win32Exception(lastError);
      }
    }

    final int nLength = lpnSize.getValue();
    final Memory securityDescriptorMemoryPointer = new Memory(nLength);
    succeeded = Advapi32.INSTANCE.GetFileSecurity(
      absoluteFilePath, infoType, securityDescriptorMemoryPointer, nLength, lpnSize);

    if (!succeeded) {
      securityDescriptorMemoryPointer.clear();
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    return securityDescriptorMemoryPointer;
  }

  /**
   * Get a self relative security descriptor for the given object type. The value is returned in Memory
   * @param absoluteObjectPath
   *         A pointer to a null-terminated string that specifies the name of the object
   *         from which to retrieve security information. For descriptions of the string
   *         formats for the different object types, see SE_OBJECT_TYPE in
   *         {@link AccCtrl.SE_OBJECT_TYPE}
   * @param objectType
   *         Object type referred to by the path. See  {@link AccCtrl.SE_OBJECT_TYPE} for valid definitions.
   * @param getSACL
   *         Get SACL of the object. See {@link Advapi32#GetNamedSecurityInfo} for process privilege requirements in getting the SACL.
   * @return Memory containing the self relative security descriptor
   */
  public static Memory getSecurityDescriptorForObject(final String absoluteObjectPath, int objectType, boolean getSACL) {

    int infoType = OWNER_SECURITY_INFORMATION
      | GROUP_SECURITY_INFORMATION
      | DACL_SECURITY_INFORMATION
      | (getSACL ? SACL_SECURITY_INFORMATION : 0);

    PointerByReference ppSecurityDescriptor = new PointerByReference();

    int lastError = Advapi32.INSTANCE.GetNamedSecurityInfo(
      absoluteObjectPath,
      objectType,
      infoType,
      null,
      null,
      null,
      null,
      ppSecurityDescriptor);

    if (lastError != 0) {
      throw new Win32Exception(lastError);
    }

    int nLength = Advapi32.INSTANCE.GetSecurityDescriptorLength(ppSecurityDescriptor.getValue());
    Memory memory = new Memory(nLength);
    Pointer secValue = ppSecurityDescriptor.getValue();
    try {
      byte[] data = secValue.getByteArray(0, nLength);
      memory.write(0, data, 0, nLength);
      return memory;
    } finally {
      Kernel32Util.freeLocalMemory(secValue);
    }
  }

  /**
   * Set a self relative security descriptor for the given object type.
   *
   * @param absoluteObjectPath
   *         A pointer to a null-terminated string that specifies the name of the object
   *         from which to retrieve security information. For descriptions of the string
   *         formats for the different object types, see {@link AccCtrl.SE_OBJECT_TYPE}.
   * @param objectType
   *         Object type referred to by the path. See  {@link AccCtrl.SE_OBJECT_TYPE} for valid definitions.
   * @param securityDescriptor
   *         A security descriptor to set.
   * @param setOwner
   *         Set the owner. The owner is extracted from securityDescriptor and must be valid,
   *         otherwise IllegalArgumentException is throw.
   *         See {@link Advapi32#SetNamedSecurityInfo} for process privilege requirements in getting the OWNER.
   * @param setGroup
   *         Set the group. The group is extracted from securityDescriptor and must be valid,
   *         otherwise IllegalArgumentException is throw.
   * @param setDACL
   *         Set the DACL. The DACL is extracted from securityDescriptor and must be valid,
   *         otherwise IllegalArgumentException is throw.
   * @param setSACL
   *         Set the SACL. The SACL is extracted from securityDescriptor and must be valid,
   *         otherwise IllegalArgumentException is throw.
   *          See {@link Advapi32#SetNamedSecurityInfo} for process privilege requirements in getting the SACL.
   * @param setDACLProtectedStatus
   *         Set DACL protected status as contained within securityDescriptor.control.
   * @param setSACLProtectedStatus
   *         Set SACL protected status as contained within securityDescriptor.control.
   */
  public static void setSecurityDescriptorForObject(final String absoluteObjectPath,
                                                    int objectType,
                                                    SECURITY_DESCRIPTOR_RELATIVE securityDescriptor,
                                                    boolean setOwner,
                                                    boolean setGroup,
                                                    boolean setDACL,
                                                    boolean setSACL,
                                                    boolean setDACLProtectedStatus,
                                                    boolean setSACLProtectedStatus) {

    final PSID psidOwner = securityDescriptor.getOwner();
    final PSID psidGroup = securityDescriptor.getGroup();
    final ACL dacl = securityDescriptor.getDiscretionaryACL();
    final ACL sacl = securityDescriptor.getSystemACL();

    int infoType = 0;
    // Parameter validation and infoType flag setting.
    if (setOwner) {
      if (psidOwner == null)
        throw new IllegalArgumentException("SECURITY_DESCRIPTOR_RELATIVE does not contain owner");
      if (!Advapi32.INSTANCE.IsValidSid(psidOwner))
        throw new IllegalArgumentException("Owner PSID is invalid");
      infoType |= OWNER_SECURITY_INFORMATION;
    }

    if (setGroup) {
      if (psidGroup == null)
        throw new IllegalArgumentException("SECURITY_DESCRIPTOR_RELATIVE does not contain group");
      if (!Advapi32.INSTANCE.IsValidSid(psidGroup))
        throw new IllegalArgumentException("Group PSID is invalid");
      infoType |= GROUP_SECURITY_INFORMATION;
    }

    if (setDACL) {
      if (dacl == null)
        throw new IllegalArgumentException("SECURITY_DESCRIPTOR_RELATIVE does not contain DACL");
      if (!Advapi32.INSTANCE.IsValidAcl(dacl.getPointer()))
        throw new IllegalArgumentException("DACL is invalid");
      infoType |= DACL_SECURITY_INFORMATION;
    }

    if (setSACL) {
      if (sacl == null)
        throw new IllegalArgumentException("SECURITY_DESCRIPTOR_RELATIVE does not contain SACL");
      if (!Advapi32.INSTANCE.IsValidAcl(sacl.getPointer()))
        throw new IllegalArgumentException("SACL is invalid");
      infoType |= SACL_SECURITY_INFORMATION;
    }

    /*
     * Control bits SE_DACL_PROTECTED/SE_SACL_PROTECTED indicate the *ACL is protected. The *ACL_SECURITY_INFORMATION flags
     * are meta flags for SetNamedSecurityInfo and are not stored in the SD.  If either *ACLProtectedStatus is set,
     * get the current status from the securityDescriptor and apply as such, otherwise the ACL remains at its default.
     */
    if (setDACLProtectedStatus) {
      if ((securityDescriptor.Control & SE_DACL_PROTECTED) != 0) {
        infoType |= PROTECTED_DACL_SECURITY_INFORMATION;
      }
      else if ((securityDescriptor.Control & SE_DACL_PROTECTED) == 0) {
        infoType |= UNPROTECTED_DACL_SECURITY_INFORMATION;
      }
    }

    if (setSACLProtectedStatus) {
      if ((securityDescriptor.Control & SE_SACL_PROTECTED) != 0) {
        infoType |= PROTECTED_SACL_SECURITY_INFORMATION;
      } else if ((securityDescriptor.Control & SE_SACL_PROTECTED) == 0) {
        infoType |= UNPROTECTED_SACL_SECURITY_INFORMATION;
      }
    }

    int lastError = Advapi32.INSTANCE.SetNamedSecurityInfo(
      absoluteObjectPath,
      objectType,
      infoType,
      setOwner ? psidOwner.getPointer() : null,
      setGroup ? psidGroup.getPointer() : null,
      setDACL ? dacl.getPointer() : null,
      setSACL ? sacl.getPointer() : null);

    if (lastError != 0) {
      throw new Win32Exception(lastError);
    }
  }

  /**
   * Checks if the current process has the given permission for the file.
   * @param file the file to check
   * @param permissionToCheck the permission to check for the file
   * @return true if has access, otherwise false
   */
  public static boolean accessCheck(File file, AccessCheckPermission permissionToCheck) {
    Memory securityDescriptorMemoryPointer = getSecurityDescriptorForFile(file.getAbsolutePath().replace('/', '\\'));

    HANDLEByReference openedAccessToken = new HANDLEByReference();
    HANDLEByReference duplicatedToken = new HANDLEByReference();
    Win32Exception err = null;
    try {
      int desireAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ;
      HANDLE hProcess = Kernel32.INSTANCE.GetCurrentProcess();
      if (!Advapi32.INSTANCE.OpenProcessToken(hProcess, desireAccess, openedAccessToken)) {
        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
      }

      if (!Advapi32.INSTANCE.DuplicateToken(openedAccessToken.getValue(), SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, duplicatedToken)) {
        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
      }

      GENERIC_MAPPING mapping = new GENERIC_MAPPING();
      mapping.genericRead = new DWORD(FILE_GENERIC_READ);
      mapping.genericWrite = new DWORD(FILE_GENERIC_WRITE);
      mapping.genericExecute = new DWORD(FILE_GENERIC_EXECUTE);
      mapping.genericAll = new DWORD(FILE_ALL_ACCESS);

      DWORDByReference rights = new DWORDByReference(new DWORD(permissionToCheck.getCode()));
      Advapi32.INSTANCE.MapGenericMask(rights, mapping);

      PRIVILEGE_SET privileges = new PRIVILEGE_SET(1);
      privileges.PrivilegeCount = new DWORD(0);
      DWORDByReference privilegeLength = new DWORDByReference(new DWORD(privileges.size()));

      DWORDByReference grantedAccess = new DWORDByReference();
      BOOLByReference result = new BOOLByReference();
      if (!Advapi32.INSTANCE.AccessCheck(securityDescriptorMemoryPointer,
        duplicatedToken.getValue(),
        rights.getValue(),
        mapping,
        privileges, privilegeLength, grantedAccess, result)) {
        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
      }

      return result.getValue().booleanValue();
    } catch (Win32Exception e) {
      err = e;
      throw err;  // re-throw so finally block executed
    } finally {
      try {
        Kernel32Util.closeHandleRefs(openedAccessToken, duplicatedToken);
      } catch (Win32Exception e) {
        if (err == null) {
          err = e;
        } else {
          err.addSuppressedReflected(e);
        }
      }

      if (securityDescriptorMemoryPointer != null) {
        securityDescriptorMemoryPointer.clear();
      }

      if (err != null) {
        throw err;
      }
    }
  }

  /**
   * Gets a file's Security Descriptor. Convenience wrapper getSecurityDescriptorForObject.
   *
   * @param file
   *         File object containing a path to a file system object.
   * @param getSACL
   *         Get the SACL. See {@link Advapi32#GetNamedSecurityInfo} for process privilege requirements in getting the SACL.
   * @return The file's Security Descriptor in self relative format.
   */
  public static SECURITY_DESCRIPTOR_RELATIVE getFileSecurityDescriptor(File file, boolean getSACL)
  {
    SECURITY_DESCRIPTOR_RELATIVE sdr;
    Memory securityDesc = getSecurityDescriptorForObject(file.getAbsolutePath().replaceAll("/", "\\"), AccCtrl.SE_OBJECT_TYPE.SE_FILE_OBJECT, getSACL);
    sdr = new SECURITY_DESCRIPTOR_RELATIVE(securityDesc);
    return sdr;
  }

  /**
   * Sets a file's Security Descriptor. Convenience wrapper setSecurityDescriptorForObject.
   * @param file
   *         File object containing a path to a file system object.
   * @param securityDescriptor
   *         The security descriptor to set.
   * @param setOwner
   *         Set the owner. See {@link Advapi32#SetNamedSecurityInfo} for process privilege requirements in setting the owner.
   * @param setGroup
   *         Set the group.
   * @param setDACL
   *         Set the DACL.
   * @param setSACL
   *         Set the SACL. See {@link Advapi32#SetNamedSecurityInfo} for process privilege requirements in setting the SACL.
   * @param setDACLProtectedStatus
   *         Set DACL protected status as contained within securityDescriptor.control.
   * @param setSACLProtectedStatus
   *         Set SACL protected status as contained within securityDescriptor.control.     *
   */
  public static void setFileSecurityDescriptor(
    File file,
    SECURITY_DESCRIPTOR_RELATIVE securityDescriptor,
    boolean setOwner,
    boolean setGroup,
    boolean setDACL,
    boolean setSACL,
    boolean setDACLProtectedStatus,
    boolean setSACLProtectedStatus)
  {
    setSecurityDescriptorForObject(file.getAbsolutePath().replaceAll("/", "\\"), AccCtrl.SE_OBJECT_TYPE.SE_FILE_OBJECT, securityDescriptor, setOwner, setGroup, setDACL, setSACL, setDACLProtectedStatus, setSACLProtectedStatus);
  }

  /**
   * Encrypts a file or directory.
   *
   * @param file
   *         The file or directory to encrypt.
   */
  public static void encryptFile(File file) {
    String lpFileName = file.getAbsolutePath();
    if (!Advapi32.INSTANCE.EncryptFile(lpFileName)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }
  }

  /**
   * Decrypts an encrypted file or directory.
   *
   * @param file
   *         The file or directory to decrypt.
   */
  public static void decryptFile(File file) {
    String lpFileName = file.getAbsolutePath();
    if (!Advapi32.INSTANCE.DecryptFile(lpFileName, new DWORD(0))) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }
  }

  /**
   * Checks the encryption status of a file.
   *
   * @param file
   *         The file to check the status for.
   * @return The status of the file.
   */
  public static int fileEncryptionStatus(File file) {
    DWORDByReference status = new DWORDByReference();
    String lpFileName = file.getAbsolutePath();
    if (!Advapi32.INSTANCE.FileEncryptionStatus(lpFileName, status)) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }
    return status.getValue().intValue();
  }

  /**
   * Disables or enables encryption of the specified directory and the files in
   * it.
   *
   * @param directory
   *         The directory for which to enable or disable encryption.
   * @param disable
   *         TRUE to disable encryption. FALSE to enable it.
   */
  public static void disableEncryption(File directory, boolean disable) {
    String dirPath = directory.getAbsolutePath();
    if (!Advapi32.INSTANCE.EncryptionDisable(dirPath, disable)) {
      throw new Win32Exception(Native.getLastError());
    }
  }

  /**
   * Backup an encrypted file or folder without decrypting it. A file named
   * "bar/sample.text" will be backed-up to "destDir/sample.text". A directory
   * named "bar" will be backed-up to "destDir/bar". This method is NOT
   * recursive. If you have an encrypted directory with encrypted files, this
   * method must be called once for the directory, and once for each encrypted
   * file to be backed-up.
   *
   * @param src
   *         The encrypted file or directory to backup.
   * @param destDir
   *         The directory where the backup will be saved.
   */
  public static void backupEncryptedFile(File src, File destDir) {
    if (!destDir.isDirectory()) {
      throw new IllegalArgumentException("destDir must be a directory.");
    }

    ULONG readFlag = new ULONG(0); // Open the file for export (backup)
    ULONG writeFlag = new ULONG(CREATE_FOR_IMPORT); // Import (restore) file

    if (src.isDirectory()) {
      writeFlag.setValue(CREATE_FOR_IMPORT | CREATE_FOR_DIR);
    }

    // open encrypted file for export
    String srcFileName = src.getAbsolutePath();
    PointerByReference pvContext = new PointerByReference();
    if (Advapi32.INSTANCE.OpenEncryptedFileRaw(srcFileName, readFlag,
      pvContext) != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    // read encrypted file
    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    FE_EXPORT_FUNC pfExportCallback = new FE_EXPORT_FUNC() {
      @Override
      public DWORD callback(Pointer pbData, Pointer pvCallbackContext,
                            ULONG ulLength) {
        byte[] arr = pbData.getByteArray(0, ulLength.intValue());
        try {
          outputStream.write(arr);
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
        return new DWORD(W32Errors.ERROR_SUCCESS);
      }
    };

    if (Advapi32.INSTANCE.ReadEncryptedFileRaw(pfExportCallback, null,
      pvContext.getValue()) != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    // close
    try {
      outputStream.close();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    Advapi32.INSTANCE.CloseEncryptedFileRaw(pvContext.getValue());

    // open file for import
    String destFileName = destDir.getAbsolutePath() + File.separator
      + src.getName();
    pvContext = new PointerByReference();
    if (Advapi32.INSTANCE.OpenEncryptedFileRaw(destFileName, writeFlag,
      pvContext) != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    // write encrypted file
    final IntByReference elementsReadWrapper = new IntByReference(0);
    FE_IMPORT_FUNC pfImportCallback = new FE_IMPORT_FUNC() {
      @Override
      public DWORD callback(Pointer pbData, Pointer pvCallbackContext,
                            ULONGByReference ulLength) {
        int elementsRead = elementsReadWrapper.getValue();
        int remainingElements = outputStream.size() - elementsRead;
        int length = Math.min(remainingElements, ulLength.getValue().intValue());
        pbData.write(0, outputStream.toByteArray(), elementsRead,
          length);
        elementsReadWrapper.setValue(elementsRead + length);
        ulLength.setValue(new ULONG(length));
        return new DWORD(W32Errors.ERROR_SUCCESS);
      }
    };

    if (Advapi32.INSTANCE.WriteEncryptedFileRaw(pfImportCallback, null,
      pvContext.getValue()) != W32Errors.ERROR_SUCCESS) {
      throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    }

    // close
    Advapi32.INSTANCE.CloseEncryptedFileRaw(pvContext.getValue());
  }

  /**
   * Convenience class to enable certain Windows process privileges
   */
  public static class Privilege implements Closeable {
    /**
     * If true, the thread is currently impersonating
     */
    private boolean currentlyImpersonating = false;

    /**
     * If true, the privileges have been enabled
     */
    private boolean privilegesEnabled = false;

    /**
     * LUID form of the privileges
     */
    private final WinNT.LUID[] pLuids;

    /**
     * Construct and enable a set of privileges
     * @param privileges the names of the privileges in the form of SE_* from Advapi32.java
     * @throws IllegalArgumentException
     */
    public Privilege(String... privileges) throws IllegalArgumentException, Win32Exception {
      pLuids = new WinNT.LUID[privileges.length];
      int i = 0;
      for (String p : privileges) {
        pLuids[i] = new WinNT.LUID();
        if (!Advapi32.INSTANCE.LookupPrivilegeValue(null, p, pLuids[i])) {
          throw new IllegalArgumentException("Failed to find privilege \"" + privileges[i] + "\" - " + Kernel32.INSTANCE.GetLastError());
        }
        i++;
      }
    }

    /**
     * Calls disable() to remove the privileges
     * @see java.io.Closeable#close()
     */
    @Override
    public void close() {
      this.disable();
    }

    /**
     * Enables the given privileges. If required, it will duplicate the process token. No resources are left open when this completes. That is, it is
     * NOT required to drop the privileges, although it is considered a best practice if you do not need it. This class is state full. It keeps track
     * of whether it has enabled the privileges. Multiple calls to enable() without a drop() in between have no affect.
     * @return pointer to self (Privilege) as a convenience for try with resources statements
     * @throws Win32Exception
     */
    public Privilege enable() throws Win32Exception {
      // Ignore if already enabled.
      if (privilegesEnabled)
        return this;

      // Get thread token
      final HANDLEByReference phThreadToken = new HANDLEByReference();

      try {
        phThreadToken.setValue(getThreadToken());
        WinNT.TOKEN_PRIVILEGES tp = new WinNT.TOKEN_PRIVILEGES(pLuids.length);
        for (int i = 0; i < pLuids.length; i++) {
          tp.Privileges[i] = new WinNT.LUID_AND_ATTRIBUTES(pLuids[i], new DWORD(WinNT.SE_PRIVILEGE_ENABLED));
        }
        if (!Advapi32.INSTANCE.AdjustTokenPrivileges(phThreadToken.getValue(), false, tp, 0, null, null)) {
          throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }
        privilegesEnabled = true;
      }
      catch (Win32Exception ex) {
        // If fails, clean up
        if (currentlyImpersonating) {
          Advapi32.INSTANCE.SetThreadToken(null, null);
          currentlyImpersonating = false;
        }
        else {
          if (privilegesEnabled) {
            WinNT.TOKEN_PRIVILEGES tp = new WinNT.TOKEN_PRIVILEGES(pLuids.length);
            for (int i = 0; i < pLuids.length; i++) {
              tp.Privileges[i] = new WinNT.LUID_AND_ATTRIBUTES(pLuids[i], new DWORD(0));
            }
            Advapi32.INSTANCE.AdjustTokenPrivileges(phThreadToken.getValue(), false, tp, 0, null, null);
            privilegesEnabled = false;
          }
        }
        throw ex;
      }
      finally {
        // Always close the thread token
        if ((!WinBase.INVALID_HANDLE_VALUE.equals(phThreadToken.getValue()))
          && (phThreadToken.getValue() != null)) {
          Kernel32.INSTANCE.CloseHandle(phThreadToken.getValue());
          phThreadToken.setValue(null);
        }
      }
      return this;
    }

    /**
     * Disabled the prior enabled privilege
     * @throws Win32Exception
     */
    public void disable() throws Win32Exception {
      // Get thread token
      final HANDLEByReference phThreadToken = new HANDLEByReference();

      try {
        phThreadToken.setValue(getThreadToken());
        if (currentlyImpersonating) {
          Advapi32.INSTANCE.SetThreadToken(null, null);
        }
        else
        {
          if (privilegesEnabled) {
            WinNT.TOKEN_PRIVILEGES tp = new WinNT.TOKEN_PRIVILEGES(pLuids.length);
            for (int i = 0; i < pLuids.length; i++) {
              tp.Privileges[i] = new WinNT.LUID_AND_ATTRIBUTES(pLuids[i], new DWORD(0));
            }
            Advapi32.INSTANCE.AdjustTokenPrivileges(phThreadToken.getValue(), false, tp, 0, null, null);
            privilegesEnabled = false;
          }
        }
      }
      finally {
        // Close the thread token
        if ((!WinBase.INVALID_HANDLE_VALUE.equals(phThreadToken.getValue()))
          && (phThreadToken.getValue() != null)) {
          Kernel32.INSTANCE.CloseHandle(phThreadToken.getValue());
          phThreadToken.setValue(null);
        }
      }
    }

    /**
     * Get a handle to the thread token. May duplicate the process token
     * and set as the thread token if the thread has no token.
     * @return HANDLE to the thread token
     * @throws Win32Exception
     */
    private HANDLE getThreadToken() throws Win32Exception {
      // we need to create a new token here for the duplicate
      final HANDLEByReference phThreadToken = new HANDLEByReference();
      final HANDLEByReference phProcessToken = new HANDLEByReference();

      try {
        // open thread token
        if (!Advapi32.INSTANCE.OpenThreadToken(Kernel32.INSTANCE.GetCurrentThread(),
          TOKEN_ADJUST_PRIVILEGES,
          false,
          phThreadToken)) {
          // OpenThreadToken may fail with W32Errors.ERROR_NO_TOKEN if current thread is anonymous. Check for that condition here. If not, throw an error.
          int lastError = Kernel32.INSTANCE.GetLastError();
          if (W32Errors.ERROR_NO_TOKEN != lastError) {
            throw new Win32Exception(lastError);
          }

          // Due to ERROR_NO_TOKEN, we need to open the process token to duplicate it, then set our thread token.
          if (!Advapi32.INSTANCE.OpenProcessToken(Kernel32.INSTANCE.GetCurrentProcess(), TOKEN_DUPLICATE, phProcessToken)) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
          }

          // Process token opened, now duplicate
          if (!Advapi32.INSTANCE.DuplicateTokenEx(phProcessToken.getValue(),
            TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE,
            null,
            SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
            TOKEN_TYPE.TokenImpersonation,
            phThreadToken)) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
          }

          // And set thread token.
          if (!Advapi32.INSTANCE.SetThreadToken(null, phThreadToken.getValue())) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
          }
          currentlyImpersonating = true;
        }
      }
      catch (Win32Exception ex) {
        // Close the thread token
        if ((!WinBase.INVALID_HANDLE_VALUE.equals(phThreadToken.getValue()))
          && (phThreadToken.getValue() != null)) {
          Kernel32.INSTANCE.CloseHandle(phThreadToken.getValue());
          phThreadToken.setValue(null);
        }
        throw ex;
      }
      finally
      {
        // Always close the process token
        if ((!WinBase.INVALID_HANDLE_VALUE.equals(phProcessToken.getValue()))
          && (phProcessToken.getValue() != null)) {
          Kernel32.INSTANCE.CloseHandle(phProcessToken.getValue());
          phProcessToken.setValue(null);
        }
      }

      return phThreadToken.getValue();
    }
  }

  /**
   * Reports whether the current process is running with elevated permissions.
   *
   * @return true if the current process has elevated perissions, false otherwise.
   */
  public static boolean isCurrentProcessElevated() {
    HANDLEByReference hToken = new HANDLEByReference();
    IntByReference returnLength = new IntByReference();
    if (Advapi32.INSTANCE.OpenProcessToken(Kernel32.INSTANCE.GetCurrentProcess(), WinNT.TOKEN_QUERY,
      hToken)) {
      try {
        TOKEN_ELEVATION elevation = new TOKEN_ELEVATION();
        if (Advapi32.INSTANCE.GetTokenInformation(hToken.getValue(),
          WinNT.TOKEN_INFORMATION_CLASS.TokenElevation, elevation, elevation.size(), returnLength)) {
          return elevation.TokenIsElevated > 0;
        }
      } finally {
        Kernel32.INSTANCE.CloseHandle(hToken.getValue());
      }
    }
    return false;
  }
}
